"""
Notification Processor
Handles email queue processing and digest generation
"""

import asyncio
import logging
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
import json

from email_service.email_manager import EmailManager
from email_service.template_manager import TemplateManager
from services.database_manager import DatabaseManager
from config import get_config

logger = logging.getLogger(__name__)

class NotificationProcessor:
    """
    Processes email notifications and manages email queue
    """
    
    def __init__(self):
        self.config = get_config()
        self.email_manager = EmailManager()
        self.template_manager = TemplateManager()
        self.db_manager = DatabaseManager()
        
        self.processing = False
        self.queue_task = None
        self.batch_size = 50
        self.processing_interval = 30  # seconds
    
    async def initialize(self):
        """Initialize notification processor"""
        try:
            await self.email_manager.initialize()
            await self.template_manager.initialize()
            logger.info("Notification processor initialized")
        except Exception as e:
            logger.error(f"Notification processor initialization failed: {e}")
            raise
    
    async def start_processing(self):
        """Start background queue processing"""
        if not self.processing:
            self.processing = True
            self.queue_task = asyncio.create_task(self._process_queue_loop())
            logger.info("Started notification queue processing")
    
    async def stop_processing(self):
        """Stop background queue processing"""
        self.processing = False
        if self.queue_task:
            self.queue_task.cancel()
            try:
                await self.queue_task
            except asyncio.CancelledError:
                pass
        logger.info("Stopped notification queue processing")
    
    async def _process_queue_loop(self):
        """Main queue processing loop"""
        while self.processing:
            try:
                await self._process_pending_notifications()
                await asyncio.sleep(self.processing_interval)
            except Exception as e:
                logger.error(f"Queue processing error: {e}")
                await asyncio.sleep(self.processing_interval)
    
    async def _process_pending_notifications(self):
        """Process pending notifications in the queue"""
        try:
            # Get pending notifications
            pending_notifications = await self._get_pending_notifications()
            
            if not pending_notifications:
                return
            
            logger.info(f"Processing {len(pending_notifications)} pending notifications")
            
            # Process in batches
            for i in range(0, len(pending_notifications), self.batch_size):
                batch = pending_notifications[i:i + self.batch_size]
                await self._process_notification_batch(batch)
                
                # Small delay between batches for rate limiting
                await asyncio.sleep(1)
            
        except Exception as e:
            logger.error(f"Failed to process pending notifications: {e}")
    
    async def _get_pending_notifications(self) -> List[Dict[str, Any]]:
        """Get pending notifications from database queue"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        SELECT * FROM notification_queue 
                        WHERE status = 'pending' 
                        AND send_at <= NOW()
                        ORDER BY created_at ASC
                        LIMIT %s
                    """, (self.batch_size * 2,))
                    
                    results = await cur.fetchall()
                    return [dict(row) for row in results]
                    
        except Exception as e:
            logger.error(f"Failed to get pending notifications: {e}")
            return []
    
    async def _process_notification_batch(self, batch: List[Dict[str, Any]]):
        """Process a batch of notifications"""
        tasks = []
        
        for notification in batch:
            task = self._process_single_notification(notification)
            tasks.append(task)
        
        # Process batch concurrently
        await asyncio.gather(*tasks, return_exceptions=True)
    
    async def _process_single_notification(self, notification: Dict[str, Any]):
        """Process a single notification"""
        try:
            notification_id = notification['id']
            
            # Update status to processing
            await self._update_notification_status(notification_id, 'processing')
            
            # Get notification data
            notification_data = json.loads(notification.get('data', '{}'))
            
            # Process based on notification type
            if notification['notification_type'] == 'email':
                await self._send_email_notification(notification, notification_data)
            elif notification['notification_type'] == 'digest':
                await self._send_digest_notification(notification, notification_data)
            elif notification['notification_type'] == 'alert':
                await self._send_alert_notification(notification, notification_data)
            else:
                logger.warning(f"Unknown notification type: {notification['notification_type']}")
                await self._update_notification_status(notification_id, 'failed', 'Unknown notification type')
                return
            
            # Mark as sent
            await self._update_notification_status(notification_id, 'sent')
            
        except Exception as e:
            logger.error(f"Failed to process notification {notification.get('id')}: {e}")
            await self._update_notification_status(
                notification.get('id'), 
                'failed', 
                str(e)
            )
    
    async def _send_email_notification(self, notification: Dict[str, Any], data: Dict[str, Any]):
        """Send email notification"""
        try:
            # Get user email
            user_email = await self._get_user_email(notification['user_id'])
            if not user_email:
                raise ValueError(f"User email not found for user {notification['user_id']}")
            
            # Get template
            template_name = data.get('template_name', 'default')
            template = await self.template_manager.get_template(template_name)
            
            if not template:
                raise ValueError(f"Template not found: {template_name}")
            
            # Render template with data
            rendered_content = await self.template_manager.render_template(
                template_name, 
                data.get('template_data', {})
            )
            
            # Send email
            result = await self.email_manager.send_email(
                to_email=user_email,
                subject=rendered_content['subject'],
                html_content=rendered_content['html'],
                text_content=rendered_content.get('text'),
                priority=data.get('priority', 'normal')
            )
            
            if not result.get('success'):
                raise Exception(result.get('error', 'Email sending failed'))
            
            # Log email delivery
            await self._log_email_delivery(
                user_id=notification['user_id'],
                email=user_email,
                template_name=template_name,
                status='delivered',
                message_id=result.get('message_id')
            )
            
        except Exception as e:
            logger.error(f"Email notification failed: {e}")
            raise
    
    async def _send_digest_notification(self, notification: Dict[str, Any], data: Dict[str, Any]):
        """Send digest notification"""
        try:
            digest_type = data.get('digest_type', 'daily')
            user_id = notification['user_id']
            
            # Generate digest content
            digest_content = await self._generate_digest_content(user_id, digest_type)
            
            if not digest_content.get('papers'):
                logger.info(f"No papers for {digest_type} digest for user {user_id}")
                return
            
            # Get user email and preferences
            user_email = await self._get_user_email(user_id)
            if not user_email:
                raise ValueError(f"User email not found for user {user_id}")
            
            # Render digest template
            template_data = {
                'user_id': user_id,
                'digest_type': digest_type,
                'papers': digest_content['papers'],
                'total_papers': len(digest_content['papers']),
                'period': digest_content['period']
            }
            
            rendered_content = await self.template_manager.render_template(
                f'{digest_type}_digest', 
                template_data
            )
            
            # Send digest email
            result = await self.email_manager.send_email(
                to_email=user_email,
                subject=rendered_content['subject'],
                html_content=rendered_content['html'],
                text_content=rendered_content.get('text'),
                priority='normal'
            )
            
            if not result.get('success'):
                raise Exception(result.get('error', 'Digest email sending failed'))
            
            # Log email delivery
            await self._log_email_delivery(
                user_id=user_id,
                email=user_email,
                template_name=f'{digest_type}_digest',
                status='delivered',
                message_id=result.get('message_id')
            )
            
        except Exception as e:
            logger.error(f"Digest notification failed: {e}")
            raise
    
    async def _send_alert_notification(self, notification: Dict[str, Any], data: Dict[str, Any]):
        """Send alert notification"""
        try:
            alert_data = data.get('alert_data', {})
            
            # Get user email
            user_email = await self._get_user_email(notification['user_id'])
            if not user_email:
                raise ValueError(f"User email not found for user {notification['user_id']}")
            
            # Render alert template
            rendered_content = await self.template_manager.render_template(
                'paper_alert', 
                alert_data
            )
            
            # Send alert email
            result = await self.email_manager.send_email(
                to_email=user_email,
                subject=rendered_content['subject'],
                html_content=rendered_content['html'],
                text_content=rendered_content.get('text'),
                priority='high'
            )
            
            if not result.get('success'):
                raise Exception(result.get('error', 'Alert email sending failed'))
            
            # Log email delivery
            await self._log_email_delivery(
                user_id=notification['user_id'],
                email=user_email,
                template_name='paper_alert',
                status='delivered',
                message_id=result.get('message_id')
            )
            
        except Exception as e:
            logger.error(f"Alert notification failed: {e}")
            raise
    
    async def _generate_digest_content(self, user_id: str, digest_type: str) -> Dict[str, Any]:
        """Generate content for digest emails"""
        try:
            # Determine time period
            if digest_type == 'daily':
                start_date = datetime.utcnow() - timedelta(days=1)
            elif digest_type == 'weekly':
                start_date = datetime.utcnow() - timedelta(weeks=1)
            elif digest_type == 'monthly':
                start_date = datetime.utcnow() - timedelta(days=30)
            else:
                start_date = datetime.utcnow() - timedelta(days=1)
            
            # Get user recommendations for the period
            recommended_papers = await self._get_user_digest_papers(user_id, start_date)
            
            return {
                'papers': recommended_papers,
                'period': {
                    'start': start_date.isoformat(),
                    'end': datetime.utcnow().isoformat(),
                    'type': digest_type
                }
            }
            
        except Exception as e:
            logger.error(f"Failed to generate digest content: {e}")
            return {'papers': []}
    
    async def _get_user_digest_papers(self, user_id: str, start_date: datetime) -> List[Dict[str, Any]]:
        """Get papers for user digest"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        SELECT 
                            p.*,
                            ur.score,
                            ur.reasons
                        FROM user_recommendations ur
                        JOIN papers p ON ur.paper_id = p.id
                        WHERE ur.user_id = %s
                        AND p.published_date >= %s
                        AND p.is_active = true
                        ORDER BY ur.score DESC, p.published_date DESC
                        LIMIT 20
                    """, (user_id, start_date))
                    
                    results = await cur.fetchall()
                    
                    papers = []
                    for row in results:
                        paper = dict(row)
                        paper['authors'] = json.loads(paper['authors']) if paper['authors'] else []
                        paper['categories'] = json.loads(paper['categories']) if paper['categories'] else []
                        paper['reasons'] = json.loads(paper['reasons']) if paper['reasons'] else []
                        papers.append(paper)
                    
                    return papers
                    
        except Exception as e:
            logger.error(f"Failed to get digest papers for user {user_id}: {e}")
            return []
    
    async def _get_user_email(self, user_id: str) -> Optional[str]:
        """Get user email address"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        SELECT email FROM user_profiles 
                        WHERE user_id = %s AND is_active = true
                    """, (user_id,))
                    
                    result = await cur.fetchone()
                    return result['email'] if result else None
                    
        except Exception as e:
            logger.error(f"Failed to get user email for {user_id}: {e}")
            return None
    
    async def _update_notification_status(
        self, 
        notification_id: str, 
        status: str, 
        error_message: Optional[str] = None
    ):
        """Update notification status in database"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    if status == 'sent':
                        await cur.execute("""
                            UPDATE notification_queue 
                            SET status = %s, sent_at = NOW()
                            WHERE id = %s
                        """, (status, notification_id))
                    else:
                        await cur.execute("""
                            UPDATE notification_queue 
                            SET status = %s, error_message = %s
                            WHERE id = %s
                        """, (status, error_message, notification_id))
                        
        except Exception as e:
            logger.error(f"Failed to update notification status: {e}")
    
    async def _log_email_delivery(
        self,
        user_id: str,
        email: str,
        template_name: str,
        status: str,
        message_id: Optional[str] = None
    ):
        """Log email delivery for analytics"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        INSERT INTO email_delivery_logs 
                        (user_id, email, template_name, status, message_id, sent_at, created_at)
                        VALUES (%s, %s, %s, %s, %s, NOW(), NOW())
                    """, (user_id, email, template_name, status, message_id))
                    
        except Exception as e:
            logger.error(f"Failed to log email delivery: {e}")
    
    # Public API methods
    async def process_single_email(self, email_data: Dict[str, Any]):
        """Process a single email request"""
        try:
            # Add to notification queue
            await self._add_to_notification_queue({
                'notification_type': 'email',
                'user_id': email_data.get('user_id'),
                'title': email_data.get('subject', ''),
                'message': email_data.get('subject', ''),
                'data': json.dumps({
                    'template_name': email_data.get('template_name'),
                    'template_data': email_data.get('template_data', {}),
                    'to_email': email_data.get('to_email'),
                    'priority': email_data.get('priority', 'normal')
                })
            })
            
        except Exception as e:
            logger.error(f"Failed to process single email: {e}")
            raise
    
    async def process_bulk_emails(self, bulk_data: Dict[str, Any]):
        """Process bulk email request"""
        try:
            recipient_data = bulk_data.get('recipient_data', [])
            
            for recipient in recipient_data:
                await self._add_to_notification_queue({
                    'notification_type': 'email',
                    'user_id': recipient.get('user_id'),
                    'title': f"Bulk email: {bulk_data.get('template_name')}",
                    'message': 'Bulk email notification',
                    'data': json.dumps({
                        'template_name': bulk_data.get('template_name'),
                        'template_data': recipient,
                        'to_email': recipient.get('email'),
                        'priority': bulk_data.get('priority', 'normal')
                    })
                })
            
        except Exception as e:
            logger.error(f"Failed to process bulk emails: {e}")
            raise
    
    async def generate_and_send_digest(self, digest_type: str, user_ids: Optional[List[str]] = None):
        """Generate and send digest emails"""
        try:
            if user_ids is None:
                # Get all eligible users
                user_ids = await self._get_digest_eligible_users(digest_type)
            
            for user_id in user_ids:
                await self._add_to_notification_queue({
                    'notification_type': 'digest',
                    'user_id': user_id,
                    'title': f'{digest_type.capitalize()} Digest',
                    'message': f'Your {digest_type} paper digest is ready',
                    'data': json.dumps({
                        'digest_type': digest_type
                    })
                })
            
            logger.info(f"Queued {len(user_ids)} {digest_type} digest emails")
            
        except Exception as e:
            logger.error(f"Failed to generate digest: {e}")
            raise
    
    async def _get_digest_eligible_users(self, digest_type: str) -> List[str]:
        """Get users eligible for digest emails"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    # Get users who have enabled this type of digest
                    await cur.execute("""
                        SELECT user_id FROM user_profiles 
                        WHERE is_active = true
                        AND notification_preferences->>'email_notifications' = 'true'
                        AND notification_preferences->>%s = 'true'
                    """, (f'{digest_type}_digest',))
                    
                    results = await cur.fetchall()
                    return [row['user_id'] for row in results]
                    
        except Exception as e:
            logger.error(f"Failed to get digest eligible users: {e}")
            return []
    
    async def _add_to_notification_queue(self, notification_data: Dict[str, Any]):
        """Add notification to processing queue"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        INSERT INTO notification_queue 
                        (user_id, notification_type, title, message, data, status, send_at, created_at)
                        VALUES (%s, %s, %s, %s, %s, 'pending', NOW(), NOW())
                    """, (
                        notification_data.get('user_id'),
                        notification_data.get('notification_type'),
                        notification_data.get('title'),
                        notification_data.get('message'),
                        notification_data.get('data')
                    ))
                    
        except Exception as e:
            logger.error(f"Failed to add notification to queue: {e}")
            raise
    
    async def get_queue_status(self) -> Dict[str, Any]:
        """Get current queue status"""
        try:
            async with self.db_manager.get_connection() as conn:
                async with conn.cursor() as cur:
                    await cur.execute("""
                        SELECT 
                            status,
                            COUNT(*) as count
                        FROM notification_queue
                        GROUP BY status
                    """)
                    
                    results = await cur.fetchall()
                    status_counts = {row['status']: row['count'] for row in results}
                    
                    return {
                        'processing': self.processing,
                        'queue_counts': status_counts,
                        'total_pending': status_counts.get('pending', 0),
                        'batch_size': self.batch_size,
                        'processing_interval': self.processing_interval
                    }
                    
        except Exception as e:
            logger.error(f"Failed to get queue status: {e}")
            return {
                'processing': self.processing,
                'error': str(e)
            }